/*
  zm.c - zmodem protocol handling lowlevelstuff
  Copyright (C) until 1998 Chuck Forsberg (OMEN Technology Inc)
  Copyright (C) 1996, 1997 Uwe Ohse

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2, or (at your option)
  any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
  02111-1307, USA.

  originally written by Chuck Forsberg
*/
/* historical comment: -- uwe
 *   Z M . C
 *    ZMODEM protocol primitives
 *    05-09-88  Chuck Forsberg Omen Technology Inc
 *
 * Entry point Functions:
 *	zsbhdr(type, hdr) send binary header
 *	zshhdr(type, hdr) send hex header
 *	zgethdr(hdr, eflag) receive header - binary or hex
 *	zsdata(buf, len, frameend) send data
 *	zrdata(buf, len, bytes_received) receive data
 *	stohdr(pos) store position data in Txhdr
 *	long rclhdr(hdr) recover position offset from header
 */

#include "zglobal.h"

#include <stdio.h>

unsigned int Rxtimeout = 100;	/* Tenths of seconds to wait for something */

/* Globals used by ZMODEM functions */
int Rxframeind;			/* ZBIN ZBIN32, or ZHEX type of frame received */
int Rxtype;			/* Type of header received */
char Rxhdr[4];			/* Received header */
char Txhdr[4];			/* Transmitted header */
long Txpos;			/* Transmitted file position */
int Txfcs32;			/* TRUE means send binary frames with 32 bit FCS */
int Crc32t;			/* Display flag indicating 32 bit CRC being sent */
int Crc32;			/* Display flag indicating 32 bit CRC being received */
int Znulls;			/* Number of nulls to send at beginning of ZDATA hdr */
char Attn[ZATTNLEN + 1];	/* Attention string rx sends to tx on err */

static char lastsent;		/* Last char we sent */
int turbo_escape;
int bytes_per_error = 0;

static const char *frametypes[] = {
	"Carrier Lost",		/* -3 */
	"TIMEOUT",		/* -2 */
	"ERROR",		/* -1 */
#define FTOFFSET 3
	"ZRQINIT",
	"ZRINIT",
	"ZSINIT",
	"ZACK",
	"ZFILE",
	"ZSKIP",
	"ZNAK",
	"ZABORT",
	"ZFIN",
	"ZRPOS",
	"ZDATA",
	"ZEOF",
	"ZFERR",
	"ZCRC",
	"ZCHALLENGE",
	"ZCOMPL",
	"ZCAN",
	"ZFREECNT",
	"ZCOMMAND",
	"ZSTDERR",
	"xxxxx"
#define FRTYPES 22		/* Total number of frame types in this array */
	    /*  not including psuedo negative entries */
};

#define badcrc _("Bad CRC")
/* static char *badcrc = "Bad CRC"; */
static inline int noxrd7(int *func_read(int, char *, int));
static inline int zdlread(int *func_read(int, char *, int));
static int zdlread2(int, int *func_read(int, char *, int));
static inline int zgeth1(int *func_read(int, char *, int));
static void zputhex(int c, char *pos);
static inline int zgethex(int *func_read(int, char *, int));
static int zrbhdr(char *hdr, int *func_read(int, char *, int));
static int zrbhdr32(char *hdr, int *func_read(int, char *, int));
static int zrhhdr(char *hdr, int *func_read(int, char *, int));
static char zsendline_tab[256];
static int zrdat32(char *buf, int length, size_t *,
		   int *func_read(int, char *, int));
static void zsbh32(char *hdr, int type, int *func_write(int, char *, int));

extern int zmodem_requested;

void
zmodem_error(int status, int errnum, const char *message, ...)
{
	if (status)
		exit(status);
}

/*
 * Read a character from the modem line with timeout.
 *  Eat parity, XON and XOFF characters.
 */
static inline int
noxrd7(int *func_read(int, char *, int))
{
	register int c;

	for (;;) {
		if ((c = READLINE_PF(Rxtimeout, func_read)) < 0)
			return c;
		switch (c &= 0177) {
		case XON:
		case XOFF:
			continue;
		default:
			if (Zctlesc && !(c & 0140))
				continue;
		case '\r':
		case '\n':
		case ZDLE:
			return c;
		}
	}
}

static inline int
zgeth1(int *func_read(int, char *, int))
{
	register int c, n;

	if ((c = noxrd7(func_read)) < 0)
		return c;
	n = c - '0';
	if (n > 9)
		n -= ('a' - ':');
	if (n & ~0xF)
		return ERROR;
	if ((c = noxrd7(func_read)) < 0)
		return c;
	c -= '0';
	if (c > 9)
		c -= ('a' - ':');
	if (c & ~0xF)
		return ERROR;
	c += (n << 4);
	return c;
}

/* Decode two lower case hex digits into an 8 bit byte value */
static inline int
zgethex(int *func_read(int, char *, int))
{
	register int c;

	c = zgeth1(func_read);
	return c;
}

/*
 * Read a byte, checking for ZMODEM escape encoding
 *  including CAN*5 which represents a quick abort
 */
static inline int
zdlread(int *func_read(int, char *, int))
{
	int c;

	/* Quick check for non control characters */
	if ((c = READLINE_PF(Rxtimeout, func_read)) & 0140)
		return c;
	return zdlread2(c, func_read);
}

/* no, i don't like gotos. -- uwe */
static int
zdlread2(int c, int *func_read(int, char *, int))
{
	goto jump_over;		/* bad style */

      again:
	/* Quick check for non control characters */
	if ((c = READLINE_PF(Rxtimeout, func_read)) & 0140)
		return c;
      jump_over:
	switch (c) {
	case ZDLE:
		break;
	case XON:
	case (XON | 0200):
	case XOFF:
	case (XOFF | 0200):
		goto again;
	default:
		if (Zctlesc && !(c & 0140)) {
			goto again;
		}
		return c;
	}
      again2:
	if ((c = READLINE_PF(Rxtimeout, func_read)) < 0)
		return c;
	if (c == CAN && (c = READLINE_PF(Rxtimeout, func_read)) < 0)
		return c;
	if (c == CAN && (c = READLINE_PF(Rxtimeout, func_read)) < 0)
		return c;
	if (c == CAN && (c = READLINE_PF(Rxtimeout, func_read)) < 0)
		return c;
	switch (c) {
	case CAN:
		return GOTCAN;
	case ZCRCE:
	case ZCRCG:
	case ZCRCQ:
	case ZCRCW:
		return (c | GOTOR);
	case ZRUB0:
		return 0177;
	case ZRUB1:
		return 0377;
	case XON:
	case (XON | 0200):
	case XOFF:
	case (XOFF | 0200):
		goto again2;
	default:
		if (Zctlesc && !(c & 0140)) {
			goto again2;
		}
		if ((c & 0140) == 0100)
			return (c ^ 0100);
		break;
	}
	return ERROR;
}

/*
 * Send character c with ZMODEM escape sequence encoding.
 *  Escape XON, XOFF. Escape CR following @ (Telenet net escape)
 */
inline void
zsendline(int c, int *func_write(int, char *, int))
{

	switch (zsendline_tab[(unsigned) (c &= 0377)]) {
	case 0:
		xsendline(lastsent = c, func_write);
		break;
	case 1:
		xsendline(ZDLE, func_write);
		c ^= 0100;
		xsendline(lastsent = c, func_write);
		break;
	case 2:
		if ((lastsent & 0177) != '@') {
			xsendline(lastsent = c, func_write);
		} else {
			xsendline(ZDLE, func_write);
			c ^= 0100;
			xsendline(lastsent = c, func_write);
		}
		break;
	}
}

static inline void
zsendline_s(const char *s, size_t count, int *func_write(int, char *, int))
{
	const char *end = s + count;

	while (s != end) {
		int last_esc = 0;
		const char *t = s;

		while (t != end) {
			last_esc = zsendline_tab[(unsigned) ((*t) & 0377)];
			if (last_esc)
				break;
			t++;
		}
		if (t != s) {
			(*func_write) (0, s, (t - s));
			lastsent = t[-1];
			s = t;
		}
		if (last_esc) {
			int c = *s;

			switch (last_esc) {
			case 0:
				xsendline(lastsent = c, func_write);
				break;
			case 1:
				xsendline(ZDLE, func_write);
				c ^= 0100;
				xsendline(lastsent = c, func_write);
				break;
			case 2:
				if ((lastsent & 0177) != '@') {
					xsendline(lastsent = c, func_write);
				} else {
					xsendline(ZDLE, func_write);
					c ^= 0100;
					xsendline(lastsent = c, func_write);
				}
				break;
			}
			s++;
		}
	}
}

/* Send ZMODEM binary header hdr of type type */
void
zsbhdr(int type, char *hdr, int *func_write(int, char *, int),
       void *func_oflush(void))
{
	register int n;
	register unsigned short crc;

	if (type == ZDATA)
		for (n = Znulls; --n >= 0;)
			xsendline(0, func_write);

	xsendline(ZPAD, func_write);
	xsendline(ZDLE, func_write);

	Crc32t = Txfcs32;
	if (Crc32t)
		zsbh32(hdr, type, func_write);
	else {
		xsendline(ZBIN, func_write);
		zsendline(type, func_write);
		crc = updcrc(type, 0);

		for (n = 4; --n >= 0; ++hdr) {
			zsendline(*hdr, func_write);
			crc = updcrc((0377 & *hdr), crc);
		}
		crc = updcrc(0, updcrc(0, crc));
		zsendline(crc >> 8, func_write);
		zsendline(crc, func_write);
	}
	if (type != ZDATA)
		flushmo();
}

/* Send ZMODEM binary header hdr of type type */
static void
zsbh32(char *hdr, int type, int *func_write(int, char *, int))
{
	register int n;
	register unsigned long crc;

	xsendline(ZBIN32, func_write);
	zsendline(type, func_write);
	crc = 0xFFFFFFFFL;
	crc = UPDC32(type, crc);

	for (n = 4; --n >= 0; ++hdr) {
		crc = UPDC32((0377 & *hdr), crc);
		zsendline(*hdr, func_write);
	}
	crc = ~crc;
	for (n = 4; --n >= 0;) {
		zsendline((int) crc, func_write);
		crc >>= 8;
	}
}

/* Send ZMODEM HEX header hdr of type type */
void
zshhdr(int type, char *hdr, int *func_write(int, char *, int),
       void *func_oflush(void))
{
	register int n;
	register unsigned short crc;
	char s[30];
	size_t len;

	s[0] = ZPAD;
	s[1] = ZPAD;
	s[2] = ZDLE;
	s[3] = ZHEX;
	zputhex(type & 0x7f, s + 4);
	len = 6;
	Crc32t = 0;

	crc = updcrc((type & 0x7f), 0);
	for (n = 4; --n >= 0; ++hdr) {
		zputhex(*hdr, s + len);
		len += 2;
		crc = updcrc((0377 & *hdr), crc);
	}
	crc = updcrc(0, updcrc(0, crc));
	zputhex(crc >> 8, s + len);
	zputhex(crc, s + len + 2);
	len += 4;

	/* Make it printable on remote machine */
	s[len++] = 015;
	s[len++] = 0212;
	/*
	 * Uncork the remote in case a fake XOFF has stopped data flow
	 */
	if (type != ZFIN && type != ZACK) {
		s[len++] = 021;
	}
	flushmo();
	(*func_write) (0, s, len);
}

/*
 * Send binary array buf of length length, with ending ZDLE sequence frameend
 */
static const char *Zendnames[] = { "ZCRCE", "ZCRCG", "ZCRCQ", "ZCRCW" };
void
zsdata(const char *buf, size_t length, int frameend,
       int *func_write(int, char *, int), void *func_oflush(void))
{
	register unsigned short crc;

	crc = 0;
	do {
		zsendline(*buf, func_write);
		crc = updcrc((0377 & *buf), crc);
		buf++;
	} while (--length > 0);
	xsendline(ZDLE, func_write);
	xsendline(frameend, func_write);
	crc = updcrc(frameend, crc);

	crc = updcrc(0, updcrc(0, crc));
	zsendline(crc >> 8, func_write);
	zsendline(crc, func_write);
	if (frameend == ZCRCW) {
		xsendline(XON, func_write);
		flushmo();
	}
}

void
zsda32(const char *buf, size_t length, int frameend,
       int *func_write(int, char *, int), void *func_oflush(void))
{
	int c;
	unsigned long crc;
	int i;

	crc = 0xFFFFFFFFL;
	zsendline_s(buf, length, func_write);
	for (; length; length--) {
		c = *buf & 0377;
		crc = UPDC32(c, crc);
		buf++;
	}
	xsendline(ZDLE, func_write);
	xsendline(frameend, func_write);
	crc = UPDC32(frameend, crc);

	crc = ~crc;
	for (i = 4; --i >= 0;) {
		c = (int) crc;
		if (c & 0140)
			xsendline(lastsent = c, func_write);
		else
			zsendline(c, func_write);
		crc >>= 8;
	}
	if (frameend == ZCRCW) {
		xsendline(XON, func_write);
		flushmo();
	}
}

#if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ <= 4)
#  undef DEBUG_BLOCKSIZE
#endif

#ifdef DEBUG_BLOCKSIZE
struct debug_blocksize {
	int size;
	long count;
};
struct debug_blocksize blocksizes[] = {
	{32, 0},
	{64, 0},
	{128, 0},
	{256, 0},
	{512, 0},
	{1024, 0},
	{2048, 0},
	{4096, 0},
	{8192, 0},
	{0, 0}
};
static inline void
count_blk(int size)
{
	int i;

	for (i = 0; blocksizes[i].size; i++) {
		if (blocksizes[i].size == size) {
			blocksizes[i].count++;
			return;
		}
	}
	blocksizes[i].count++;
}

static void
printout_blocksizes(void)
{
	int i;

	for (i = 0; blocksizes[i].size; i++) {
		if (blocksizes[i].count) {
		}
	}
	if (blocksizes[i].count) {
	}
}

#define COUNT_BLK(x) count_blk(x)
#else
#define COUNT_BLK(x)
#endif

/*
 * Receive array buf of max length with ending ZDLE sequence
 *  and CRC.  Returns the ending character or error code.
 *  NB: On errors may store length+1 bytes!
 */
int
zrdata(char *buf, int length, size_t * bytes_received,
       int *func_read(int, char *, int))
{
	register int c;
	register unsigned short crc;
	register char *end;
	register int d;

	*bytes_received = 0;
	if (Rxframeind == ZBIN32)
		return zrdat32(buf, length, bytes_received, func_read);

	crc = 0;
	end = buf + length;
	while (buf <= end) {
		if ((c = zdlread(func_read)) & ~0377) {
		      crcfoo:
			switch (c) {
			case GOTCRCE:
			case GOTCRCG:
			case GOTCRCQ:
			case GOTCRCW:
				{
					d = c;
					c &= 0377;
					crc = updcrc(c, crc);
					if ((c = zdlread(func_read)) & ~0377)
						goto crcfoo;
					crc = updcrc(c, crc);
					if ((c = zdlread(func_read)) & ~0377)
						goto crcfoo;
					crc = updcrc(c, crc);
					if (crc & 0xFFFF) {
						return ERROR;
					}
					*bytes_received = length - (end - buf);
					COUNT_BLK(*bytes_received);
					return d;
				}
			case GOTCAN:
				return ZCAN;
			case TIMEOUT:
				return c;
			default:
				return c;
			}
		}
		*buf++ = c;
		crc = updcrc(c, crc);
	}
	return ERROR;
}

static int
zrdat32(char *buf, int length, size_t * bytes_received,
	int *func_read(int, char *, int))
{
	register int c;
	register unsigned long crc;
	register char *end;
	register int d;

	crc = 0xFFFFFFFFL;
	end = buf + length;
	while (buf <= end) {
		if ((c = zdlread(func_read)) & ~0377) {
		      crcfoo:
			switch (c) {
			case GOTCRCE:
			case GOTCRCG:
			case GOTCRCQ:
			case GOTCRCW:
				d = c;
				c &= 0377;
				crc = UPDC32(c, crc);
				if ((c = zdlread(func_read)) & ~0377)
					goto crcfoo;
				crc = UPDC32(c, crc);
				if ((c = zdlread(func_read)) & ~0377)
					goto crcfoo;
				crc = UPDC32(c, crc);
				if ((c = zdlread(func_read)) & ~0377)
					goto crcfoo;
				crc = UPDC32(c, crc);
				if ((c = zdlread(func_read)) & ~0377)
					goto crcfoo;
				crc = UPDC32(c, crc);
				if (crc != 0xDEBB20E3) {
					return ERROR;
				}
				*bytes_received = length - (end - buf);
				COUNT_BLK(*bytes_received);
				return d;
			case GOTCAN:
				return ZCAN;
			case TIMEOUT:
				return c;
			default:
				return c;
			}
		}
		*buf++ = c;
		crc = UPDC32(c, crc);
	}
	return ERROR;
}

/*
 * Read a ZMODEM header to hdr, either binary or hex.
 *  eflag controls local display of non zmodem characters:
 *	0:  no display
 *	1:  display printing characters only
 *	2:  display all non ZMODEM characters
 *  On success, set Zmodem to 1, set Rxpos and return type of header.
 *   Otherwise return negative on error.
 *   Return ERROR instantly if ZCRCW sequence, for fast error recovery.
 */
int
zgethdr(char *hdr, int eflag, size_t * Rxpos, int *func_read(int, char *, int))
{
	register int c, cancount;
	unsigned int max_garbage;	/* Max bytes before start of frame */
	size_t rxpos = 0;	/* keep gcc happy */

	max_garbage = Zrwindow + Baudrate;
	Rxframeind = Rxtype = 0;

      startover:
	cancount = 5;
      again:
	/* Return immediate ERROR if ZCRCW sequence seen */
	switch (c = READLINE_PF(Rxtimeout, func_read)) {
	case RCDO:
	case TIMEOUT:
		goto fifi;
	case CAN:
	      gotcan:
		if (--cancount <= 0) {
			c = ZCAN;
			goto fifi;
		}
		switch (c = READLINE_PF(1, func_read)) {
		case TIMEOUT:
			goto again;
		case ZCRCW:
			c = ERROR;
			/* **** FALL THRU TO **** */
		case RCDO:
			goto fifi;
		default:
			break;
		case CAN:
			if (--cancount <= 0) {
				c = ZCAN;
				goto fifi;
			}
			goto again;
		}
		/* **** FALL THRU TO **** */
	default:
	      agn2:
		if (--max_garbage == 0) {
			return (ERROR);
		}
		goto startover;
	case ZPAD | 0200:	/* This is what we want. */
	case ZPAD:		/* This is what we want. */
		break;
	}
	cancount = 5;
      splat:
	switch (c = noxrd7(func_read)) {
	case ZPAD:
		goto splat;
	case RCDO:
	case TIMEOUT:
		goto fifi;
	default:
		goto agn2;
	case ZDLE:		/* This is what we want. */
		break;
	}

	switch (c = noxrd7(func_read)) {
	case RCDO:
	case TIMEOUT:
		goto fifi;
	case ZBIN:
		Rxframeind = ZBIN;
		Crc32 = FALSE;
		c = zrbhdr(hdr, func_read);
		break;
	case ZBIN32:
		Crc32 = Rxframeind = ZBIN32;
		c = zrbhdr32(hdr, func_read);
		break;
	case ZHEX:
		Rxframeind = ZHEX;
		Crc32 = FALSE;
		c = zrhhdr(hdr, func_read);
		break;
	case CAN:
		goto gotcan;
	default:
		goto agn2;
	}
	rxpos = hdr[ZP3] & 0377;
	rxpos = (rxpos << 8) + (hdr[ZP2] & 0377);
	rxpos = (rxpos << 8) + (hdr[ZP1] & 0377);
	rxpos = (rxpos << 8) + (hdr[ZP0] & 0377);
      fifi:
	switch (c) {
	case GOTCAN:
		c = ZCAN;
		/* **** FALL THRU TO **** */
	case ZNAK:
	case ZCAN:
	case ERROR:
	case TIMEOUT:
	case RCDO:
		/* **** FALL THRU TO **** */
	default:
		;
	}
	if (Rxpos)
		*Rxpos = rxpos;
	return c;
}

/* Receive a binary style header (type and position) */
static int
zrbhdr(char *hdr, int *func_read(int, char *, int))
{
	register int c, n;
	register unsigned short crc;

	if ((c = zdlread(func_read)) & ~0377)
		return c;
	Rxtype = c;
	crc = updcrc(c, 0);

	for (n = 4; --n >= 0; ++hdr) {
		if ((c = zdlread(func_read)) & ~0377)
			return c;
		crc = updcrc(c, crc);
		*hdr = c;
	}
	if ((c = zdlread(func_read)) & ~0377)
		return c;
	crc = updcrc(c, crc);
	if ((c = zdlread(func_read)) & ~0377)
		return c;
	crc = updcrc(c, crc);
	if (crc & 0xFFFF) {
		return ERROR;
	}
	protocol = ZM_ZMODEM;
	zmodem_requested = TRUE;
	return Rxtype;
}

/* Receive a binary style header (type and position) with 32 bit FCS */
static int
zrbhdr32(char *hdr, int *func_read(int, char *, int))
{
	register int c, n;
	register unsigned long crc;

	if ((c = zdlread(func_read)) & ~0377)
		return c;
	Rxtype = c;
	crc = 0xFFFFFFFFL;
	crc = UPDC32(c, crc);

	for (n = 4; --n >= 0; ++hdr) {
		if ((c = zdlread(func_read)) & ~0377)
			return c;
		crc = UPDC32(c, crc);
		*hdr = c;
	}
	for (n = 4; --n >= 0;) {
		if ((c = zdlread(func_read)) & ~0377)
			return c;
		crc = UPDC32(c, crc);
	}
	if (crc != 0xDEBB20E3) {
		return ERROR;
	}
	protocol = ZM_ZMODEM;
	zmodem_requested = TRUE;
	return Rxtype;
}

/* Receive a hex style header (type and position) */
static int
zrhhdr(char *hdr, int *func_read(int, char *, int))
{
	register int c;
	register unsigned short crc;
	register int n;

	if ((c = zgethex(func_read)) < 0)
		return c;
	Rxtype = c;
	crc = updcrc(c, 0);

	for (n = 4; --n >= 0; ++hdr) {
		if ((c = zgethex(func_read)) < 0)
			return c;
		crc = updcrc(c, crc);
		*hdr = c;
	}
	if ((c = zgethex(func_read)) < 0)
		return c;
	crc = updcrc(c, crc);
	if ((c = zgethex(func_read)) < 0)
		return c;
	crc = updcrc(c, crc);
	if (crc & 0xFFFF) {
		return ERROR;
	}
	switch (c = READLINE_PF(1, func_read)) {
	case 0215:
		/* **** FALL THRU TO **** */
	case 015:
		/* Throw away possible cr/lf */
		READLINE_PF(1, func_read);
		break;
	}
	protocol = ZM_ZMODEM;
	zmodem_requested = TRUE;
	return Rxtype;
}

/* Write a byte as two hex digits */
static void
zputhex(int c, char *pos)
{
	static char digits[] = "0123456789abcdef";

	pos[0] = digits[(c & 0xF0) >> 4];
	pos[1] = digits[c & 0x0F];
}

void
zsendline_init(void)
{
	int i;

	for (i = 0; i < 256; i++) {
		if (i & 0140)
			zsendline_tab[i] = 0;
		else {
			switch (i) {
			case ZDLE:
			case XOFF:	/* ^Q */
			case XON:	/* ^S */
			case (XOFF | 0200):
			case (XON | 0200):
				zsendline_tab[i] = 1;
				break;
			case 020:	/* ^P */
			case 0220:
				if (turbo_escape)
					zsendline_tab[i] = 0;
				else
					zsendline_tab[i] = 1;
				break;
			case 015:
			case 0215:
				if (Zctlesc)
					zsendline_tab[i] = 1;
				else if (!turbo_escape)
					zsendline_tab[i] = 2;
				else
					zsendline_tab[i] = 0;
				break;
			default:
				if (Zctlesc)
					zsendline_tab[i] = 1;
				else
					zsendline_tab[i] = 0;
			}
		}
	}
}

/* Store pos in Txhdr */
void
stohdr(size_t pos)
{
	long lpos = (long) pos;

	Txhdr[ZP0] = lpos;
	Txhdr[ZP1] = lpos >> 8;
	Txhdr[ZP2] = lpos >> 16;
	Txhdr[ZP3] = lpos >> 24;
}

/* Recover a long integer from a header */
long
rclhdr(char *hdr)
{
	long l;

	l = (hdr[ZP3] & 0377);
	l = (l << 8) | (hdr[ZP2] & 0377);
	l = (l << 8) | (hdr[ZP1] & 0377);
	l = (l << 8) | (hdr[ZP0] & 0377);
	return l;
}

void
raw_ochar(char c, int *func_write(int, char *, int))
{
	(*func_write) (0, &c, 1);
}

/* End of zm.c */
